#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/sysfs.h>

#include "ms518xn.h"

typedef struct ms518x_configure_s {
	uint32_t incc;
	uint32_t inx;
	uint32_t bw;
	uint32_t ref;
	uint32_t seq;
	uint32_t rb;
} ms518xn_configure_t;

static struct iio_info drive_ms518xn;

static const struct iio_chan_spec ms5182n_channels[] = {
	IIO_CHAN_SOFT_TIMESTAMP(4),
	MS518XN_CHANNEL(0),
	MS518XN_CHANNEL(1),
	MS518XN_CHANNEL(2),
	MS518XN_CHANNEL(3),
};

static const struct iio_chan_spec ms5189n_channels[] = {
	IIO_CHAN_SOFT_TIMESTAMP(8),
	MS518XN_CHANNEL(0),
	MS518XN_CHANNEL(1),
	MS518XN_CHANNEL(2),
	MS518XN_CHANNEL(3),
	MS518XN_CHANNEL(4),
	MS518XN_CHANNEL(5),
	MS518XN_CHANNEL(6),
	MS518XN_CHANNEL(7),
};

static int
method_ms518xn_phy_reset_spi(ms518xn_phy_t* ms518xn_phy)
{
	uint16_t rst = 0x3fff << 2;

	ms518xn_phy->transfer(ms518xn_phy, &rst, NULL);
	ms518xn_phy->transfer(ms518xn_phy, &rst, NULL);

	return 0;
}

static int
method_ms518xn_phy_transfer_spi(ms518xn_phy_t* ms518xn_phy,
		uint16_t* in, uint16_t* out)
{
	struct spi_device* spi = (struct spi_device*) ms518xn_phy->phy;
	uint8_t ms518xn_tx[2];
	uint8_t ms518xn_rx[2];

	struct spi_transfer t = {
		.tx_buf = NULL,
		.rx_buf = ms518xn_rx,
		.len = sizeof(ms518xn_rx),
	};

	int ret;

	if (in != NULL)
	{
		t.tx_buf = ms518xn_tx;
		ms518xn_tx[0] = (*in ) >> 8;
		ms518xn_tx[1] = (*in ) & 0xff;
	}
	else
	{
		memset(ms518xn_tx, 0, sizeof(ms518xn_tx));
	}

	udelay(4);
	ret = spi_sync_transfer(spi, &t, 1);

//	dev_info(&spi->dev, "spi transfer: 0x%02x%02x <--> 0x%02x%02x\n",
//			ms518xn_tx[0], ms518xn_tx[1],
//			ms518xn_rx[0], ms518xn_rx[1]);

	if (out != NULL)
		*out = ((ms518xn_rx[0] << 8) | ms518xn_rx[1]) ;

	return 0;
}

static int
method_ms518xn_chip_sync(ms518xn_chip_t* ms518xn_chip)
{
	ms518xn_phy_t* phy = &ms518xn_chip->phy;
	uint16_t value = 0;
	uint16_t cfg = ms518xn_chip->cfg_stash;

	if (cfg == ms518xn_chip->cfg_commit)
		return 0;

	cfg = cfg | MS518XN_BIT_COVER;
	cfg = cfg << 2;

	phy->transfer(phy, &cfg, &value);
	// CFG 不会立刻生效，需要几次哑转换
	phy->transfer(phy, NULL, &value);
	phy->transfer(phy, NULL, &value);
	phy->transfer(phy, NULL, &value);

	ms518xn_chip->cfg_commit = ms518xn_chip->cfg_stash;

	return 0;
}

static int
method_ms518xn_chip_send(ms518xn_chip_t* ms518xn_chip)
{
	ms518xn_phy_t* phy = &ms518xn_chip->phy;
	uint16_t value = 0;
	uint16_t cfg = ms518xn_chip->cfg_stash;

	if (cfg == ms518xn_chip->cfg_commit)
		return 0;

	cfg = cfg & (~MS518XN_BIT_COVER);
	cfg = cfg << 2;

	phy->transfer(phy, &cfg, &value);

	ms518xn_chip->cfg_commit = ms518xn_chip->cfg_stash;

	return 0;
}

static int
method_ms518xn_chip_set_incc(ms518xn_chip_t* ms518xn_chip,
		ms518xn_incc_e incc)
{
	ms518xn_chip->cfg_stash =
		MS518XN_CFG_WR(ms518xn_chip->cfg_stash, MS518XN_BIT_INCC, incc);
	return 0;
}

static int
method_ms518xn_chip_set_inx(ms518xn_chip_t* ms518xn_chip,
		uint8_t inx)
{
	ms518xn_chip->cfg_stash =
		MS518XN_CFG_WR(ms518xn_chip->cfg_stash, MS518XN_BIT_INX, inx);
	return 0;
}

static int
method_ms518xn_chip_set_bw(ms518xn_chip_t* ms518xn_chip,
		ms518xn_bw_e bw)
{
	ms518xn_chip->cfg_stash =
		MS518XN_CFG_WR(ms518xn_chip->cfg_stash, MS518XN_BIT_BW, bw);
	return 0;
}

static int
method_ms518xn_chip_set_ref(ms518xn_chip_t* ms518xn_chip,
		ms518xn_ref_e ref)
{
	ms518xn_chip->cfg_stash =
		MS518XN_CFG_WR(ms518xn_chip->cfg_stash, MS518XN_BIT_REF, ref);
	return 0;
}

static int
method_ms518xn_chip_set_seq(ms518xn_chip_t* ms518xn_chip,
		ms518xn_seq_e seq)
{
	ms518xn_chip->cfg_stash =
		MS518XN_CFG_WR(ms518xn_chip->cfg_stash, MS518XN_BIT_SEQ, seq);
	return 0;
}

static int
method_ms518xn_chip_set_rb(ms518xn_chip_t* ms518xn_chip,
		ms518xn_rb_e rb)
{
	ms518xn_chip->cfg_stash =
		MS518XN_CFG_WR(ms518xn_chip->cfg_stash, MS518XN_BIT_RB, rb);
	return 0;
}

static void ms518xn_phy_init_use_spi(ms518xn_phy_t* ms518xn_phy)
{
	ms518xn_phy->reset = method_ms518xn_phy_reset_spi;
	ms518xn_phy->transfer = method_ms518xn_phy_transfer_spi;
}

static void ms518xn_phy_init(ms518xn_phy_t* ms518xn_phy,
		ms518xn_phy_type_e type, void* phy)
{
	ms518xn_phy->phy = phy;

	switch (type)
	{
	case MS518XN_USE_SPI_PHY:
		ms518xn_phy_init_use_spi(ms518xn_phy);
		break;
	default:
		break;
	}
}

static void ms518xn_chip_init(ms518xn_chip_t* ms518xn_chip)
{
	ms518xn_chip->sync = method_ms518xn_chip_sync;
	ms518xn_chip->send = method_ms518xn_chip_send;
	ms518xn_chip->set_incc = method_ms518xn_chip_set_incc;
	ms518xn_chip->set_inx = method_ms518xn_chip_set_inx;
	ms518xn_chip->set_bw = method_ms518xn_chip_set_bw;
	ms518xn_chip->set_ref = method_ms518xn_chip_set_ref;
	ms518xn_chip->set_seq = method_ms518xn_chip_set_seq;
	ms518xn_chip->set_rb = method_ms518xn_chip_set_rb;
}

static int ms518xn_physical_link(ms518xn_t* ms518xn,
		ms518xn_phy_type_e type, void* phy)
{
	ms518xn_chip_t* ms518xn_chip = &ms518xn->chip;
	ms518xn_phy_t* ms518xn_phy = &ms518xn_chip->phy;

	ms518xn_phy_init(ms518xn_phy, type, phy);
	ms518xn_chip_init(ms518xn_chip);

	//ms518xn->method = methodxxx;

	// MS518XN 没法获取 MS5182N MS5189N，依靠 DTS 指定
	// 代码移到 @ms518xn_cofigure

	return 0;
}

static int ms518xn_configure(ms518xn_t* ms518xn, struct device_node *node)
{
	ms518xn_chip_t* chip = &ms518xn->chip;
	ms518xn_phy_t* phy = &chip->phy;
	const char* type;
	ms518xn_configure_t configure = {
		.incc = MS518XN_INCC_UNIPOLGND,
		.inx = 0,
		.bw = MS518XN_BANDWITDH_FULL,
		.ref =MS518XN_REF_EXTDETH,
		.seq = MS518XN_SEQ_DISABLE,
		.rb = MS518XN_RB_ENABLE,
	};

	of_property_read_string(node, "ms518xn-chip-type", &type);
	if (strcmp(type, "ms5182n") == 0)
		ms518xn->type = MS5182N;
	else
		ms518xn->type = MS5189N;

//	of_property_read_u32(node, "ms518xn-chip-incc", &configure.incc);
//	of_property_read_u32(node, "ms518xn-chip-inx", &configure.inx);
//	of_property_read_u32(node, "ms518xn-chip-bw", &configure.bw);
	of_property_read_u32(node, "ms518xn-chip-ref", &configure.ref);
//	of_property_read_u32(node, "ms518xn-chip-seq", &configure.seq);
	of_property_read_u32(node, "ms518xn-chip-rb", &configure.rb);

	phy->reset(phy);

	chip->set_incc(chip, (ms518xn_incc_e)configure.incc);
	chip->set_inx(chip, configure.inx);
	chip->set_bw(chip, (ms518xn_bw_e)configure.bw);
	chip->set_ref(chip, (ms518xn_ref_e)configure.ref);
	chip->set_seq(chip, (ms518xn_seq_e)configure.seq);
	chip->set_rb(chip, (ms518xn_rb_e)configure.rb);
	chip->sync(chip);

	return 0;
}

static int ms518xn_get_scale(ms518xn_t* ms518xn)
{
	switch (MS518XN_CFG_RD_REF(ms518xn->chip.cfg_commit))
	{
	case MS518XN_REF_INNER_25:
		return 2500;
	case MS518XN_REF_INNER_4096:
		return 4096;
	default:
		return 2500;
	}
}

static int
drive_ms518xn_read_raw(struct iio_dev *indio_dev,
			    struct iio_chan_spec const *chan,
			    int *val, int *val2, long m)
{
	ms518xn_t* ms518xn = iio_priv(indio_dev);
	ms518xn_chip_t* chip = &ms518xn->chip;
	ms518xn_phy_t* phy = &chip->phy;
	uint16_t value;

	switch (m) {
	case IIO_CHAN_INFO_RAW:
		chip->set_inx(chip, (uint16_t)chan->channel);
		chip->sync(chip);
		phy->transfer(phy, NULL, &value);
		// 电压变化时会读到之前电压数值，再读一次
		phy->transfer(phy, NULL, &value);
		*val = value;
		return IIO_VAL_INT;

	case IIO_CHAN_INFO_SCALE:
		*val = ms518xn_get_scale(ms518xn);
//		*val2 = chan->scan_type.realbits;
		return IIO_VAL_INT;
	}
	return -EINVAL;
}

static void
drive_ms518xn_init(void)
{
	drive_ms518xn.read_raw = drive_ms518xn_read_raw;
}

int ms518xn_probe(struct iio_dev* iio, struct device_node* node,
		ms518xn_phy_type_e type, void* phy)
{
	ms518xn_t* ms518xn = iio_priv(iio);
	ms518xn_physical_link(ms518xn, type, phy);
	ms518xn_configure(ms518xn, node);
	drive_ms518xn_init();


	switch(ms518xn->type)
	{
	case MS5182N:
		iio->name = "MS5182N";
		iio->channels = ms5182n_channels;
		iio->num_channels = ARRAY_SIZE(ms5182n_channels);
		break;
	case MS5189N:
		iio->name = "MS5189N";
		iio->channels = ms5189n_channels;
		iio->num_channels = ARRAY_SIZE(ms5189n_channels);
		break;
	default:
		dev_info(&iio->dev, "ERRO: ms518xn get %d\n", ms518xn->type);
		return -1;
	}
	iio->modes = INDIO_DIRECT_MODE;
	iio->info = &drive_ms518xn;

	dev_info(&iio->dev, "INFO: ms518xn probe %d\n", ms518xn->type);

	return iio_device_register(iio);
}

